/**
 * Copyright 2014 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.netflix.servo.jmx;

import java.util.regex.Pattern;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.servo.util.Throwables;
import com.netflix.servo.tag.Tag;
import com.netflix.servo.tag.TagList;

/**
 * A helper class that assists in building
 * {@link ObjectName}s given monitor {@link Tag}s
 * or {@link TagList}. The builder also sanitizes
 * all values to avoid invalid input. Any characters that are
 * not alphanumeric, a period, or hypen are considered invalid
 * and are remapped to underscores.
 *
 */
final class ObjectNameBuilder {

    private static final Pattern INVALID_CHARS = Pattern.compile("[^a-zA-Z0-9_\\-\\.]");
    private static final Logger LOG = LoggerFactory.getLogger(ObjectNameBuilder.class);

    /**
     * Sanitizes a value by replacing any character that is not alphanumeric,
     * a period, or hyphen with an underscore.
     * @param value  the value to sanitize
     * @return       the sanitized value
     */
    public static String sanitizeValue(String value) {
        return INVALID_CHARS.matcher(value).replaceAll("_");
    }

    /**
     * Creates an {@link ObjectNameBuilder} given the JMX domain.
     * @param domain the JMX domain
     * @return The ObjectNameBuilder
     */
    public static ObjectNameBuilder forDomain(String domain) {
        return new ObjectNameBuilder(domain);
    }

    private final StringBuilder nameStrBuilder;

    private ObjectNameBuilder(String domain) {
        nameStrBuilder = new StringBuilder(sanitizeValue(domain));
        nameStrBuilder.append(":");
    }

    /**
     * Adds the {@link TagList} as {@link ObjectName} properties.
     * @param tagList the tag list to add
     * @return This builder
     */
    public ObjectNameBuilder addProperties(TagList tagList) {
        for (Tag tag : tagList) {
            addProperty(tag);
        }

        return this;
    }

    /**
     * Adds the {@link Tag} as a {@link ObjectName} property.
     * @param tag the tag to add
     * @return This builder
     */
    public ObjectNameBuilder addProperty(Tag tag) {
        return addProperty(tag.getKey(), tag.getValue());
    }

    /**
     * Adds the key/value as a {@link ObjectName} property.
     * @param key    the key to add
     * @param value  the value to add
     * @return This builder
     */
    public ObjectNameBuilder addProperty(String key, String value) {
        nameStrBuilder.append(sanitizeValue(key))
                      .append('=')
                      .append(sanitizeValue(value)).append(",");
        return this;
    }

    /**
     * Builds the {@link ObjectName} given the configuration.
     * @return The created ObjectName
     */
    public ObjectName build() {
        final String name = nameStrBuilder.substring(0, nameStrBuilder.length() - 1);
        try {
            return new ObjectName(name);
        } catch (MalformedObjectNameException e) {
            LOG.warn("Invalid ObjectName provided: " + name);
            throw Throwables.propagate(e);
        }
    }

}
